﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using System.Diagnostics;
using PitOfDespair.GameObjects;

namespace PitOfDespair.Dungeon
{

    class Level
    {
        private Location[,] layout;
        private bool[,] visited;

        private Vector2? entrance;

        private Dictionary<GameObjects.GameObject, Vector2> objectLocationMap =
            new Dictionary<GameObjects.GameObject, Vector2>();

        private IDictionary<PlayerIndex, Player> playerMap = new Dictionary<PlayerIndex, Player>();

        public void AddPlayer(Player player, Level previous = null)
        {
            if (previous != null)
            {
                //capture coordinates
                var oldLocation = previous.objectLocationMap[player];

                previous.RemovePlayer(player.PlayerIndex);
                AddObject(player, oldLocation);
            }
            else
                AddObject(player);
            playerMap.Add(player.PlayerIndex, player);

        }
        public Character RemovePlayer(PlayerIndex playerIndex)
        {
            var p = playerMap[playerIndex];
            playerMap.Remove(playerIndex);
            objectLocationMap.Remove(p);
            return p;
        }
        public void AddObjectRandom(GameObjects.GameObject obj)
        {
            Vector2 target;

            do
            {
                target = new Vector2(rnd.Next(layout.GetUpperBound(0)), rnd.Next(layout.GetUpperBound(1)));
            } while (playerMap.Select(s => s.Value).Select(pl => objectLocationMap[pl] == target).Any(a => a));
            objectLocationMap.Add(obj, target);
        }
        public void AddObject(GameObjects.GameObject obj, Vector2? location = null)
        {
            objectLocationMap.Add(obj, location ?? new Vector2(entrance.Value.X, entrance.Value.Y));
        }
        public Level(Random rnd, Player player, IGenerationStrategy strategy, Level previous = null)
        {
            this.rnd = rnd;
            var startX = 0;
            var startY = 0;
            if (previous == null)
            {
                var result = strategy.Generate();
                layout = result.Item1;

                startX = result.Item2;
                startY = result.Item3;

            }
            else
            {
                var oldLocation = previous.objectLocationMap[player];
                startX = (int)oldLocation.X;
                startY = (int)oldLocation.Y;
                previous.RemovePlayer(player.PlayerIndex);
                layout = strategy.Generate((int)oldLocation.X, (int)oldLocation.Y);

            }

            entrance = new Vector2(startX, startY);
            visited = new bool[layout.GetUpperBound(0) + 1, layout.GetUpperBound(1) + 1];
            Debug.Assert(layout.GetUpperBound(0) == visited.GetUpperBound(0));
            Debug.Assert(layout.GetUpperBound(1) == visited.GetUpperBound(1));
            visited[startX, startY] = true;
            //find an entrance
            var loc = layout[startX, startY];
            Debug.Assert(new[] { loc.North, loc.South, loc.West, loc.East }.Contains(WallType.Ascend));
            this.AddPlayer(player);
            Debug.Assert(entrance.HasValue);
            // if(entrance.HasValue==false)
            //     throw new ArgumentException("Invalid level generated");
            // this.entrance = entrance.Value;
        }

        public WallType MoveNorth(PlayerIndex index)
        {
            return Move(index, l => l.North, new Vector2(0, -1.0f));
        }


        public WallType MoveEast(PlayerIndex index)
        {
            return Move(index, l => l.East, new Vector2(1.0f, 0));
        }
        public WallType MoveWest(PlayerIndex index)
        {
            return Move(index, l => l.West, new Vector2(-1.0f, 0));

        }
        public WallType MoveSouth(PlayerIndex index)
        {
            return Move(index, l => l.South, new Vector2(0, 1.0f));
        }

        static Rectangle GetGridRectangle(int x, int y, Rectangle sizeWithOffset, int scale)
        {
            // Original map drawing that appears to work did not scale width or height, is this accurate?
            return new Rectangle(x * scale + sizeWithOffset.X, y * scale + sizeWithOffset.Y, sizeWithOffset.Width,
                                 sizeWithOffset.Height);
        }

        const int margin = 5;
        private Random rnd;
        internal void Draw(int scale, Action<Rectangle, Color> drawAction, Action<Texture2D, Rectangle, Color> drawTexturedAction)
        {
            for (int x = 0; x <= layout.GetUpperBound(0); x++)
                for (int y = 0; y <= layout.GetUpperBound(1); y++)
                {

                    //var closedWallTypes = Enum.GetNames(typeof(WallType)).Except(new[] {WallType.Open});
                    var loc = layout[x, y];
                    if (loc != null)
                    {
                        if (visited[x, y]) //draw a visited location
                            loc.Draw((r, c) =>


                                     drawAction(GetGridRectangle(x, y, r, scale), c)

                                     , scale);
                        else
                            loc.DrawFogged((r,c)=>
                                drawAction(GetGridRectangle(x,y,r,scale),Color.Black),scale);
                    }
                }
            foreach(var obj in objectLocationMap.Keys)
            {
                var loc = objectLocationMap[obj];
                var gridRect=GetGridRectangle((int)loc.X,(int) loc.Y,new Rectangle(0, 0, scale, scale),scale);
                
                var marker = new Rectangle(gridRect.X+gridRect.Width/4,gridRect.Y+gridRect.Height/4,gridRect.Height/2,gridRect.Width/2);
                
                if(obj is Player)
                {
                    drawTexturedAction(obj.Texture, marker, Color.White);
                    
                }
                else if (obj is Character)
                {
                    if(visited[(int)loc.X,(int)loc.Y]) //don't draw monsters that the player can't see
                    drawTexturedAction(obj.Texture, marker, Color.Red);
                }
                //spriteBatch.Draw(obj.Texture, obj.PositionOnTexture, Color.White);    
            }
            
            // b.Draw(wall, new Rectangle(100, 100, 100, 100), Color.Brown);
        }


        WallType Move(PlayerIndex i, Func<Location, WallType> wallSide, Vector2 ifMove)
        {
            var player = playerMap[i];
            var loc = objectLocationMap[player];
            var wall = wallSide(layout[(int)loc.X, (int)loc.Y]);
            if (wall == WallType.Open)
            {
                var newLocation = loc + ifMove;
                var creature = objectLocationMap.FirstOrDefault(a => a.Value == newLocation);
                if (creature.Key != null && (creature.Key is Player) == false && creature.Key is Character) //will need to add logic for/if pvp
                {
                    DoAttack(i, (Character)creature.Key);
                    //don't return if we want the character to move if the creature is defeated?
                    return WallType.Open;
                }

                var x = (int)newLocation.X;
                var y = (int)newLocation.Y;

                objectLocationMap[player] = newLocation;

                visited[x, y] = true;
                var room = layout[x, y];
                if (room.Treasure > 0)
                {
                    playerMap[i].Score += room.Treasure;
                    room.Treasure = 0;
                }
                if (room.East == WallType.Open && visited[x + 1, y] == false)
                    visited[x + 1, y] = true;
                if (room.West == WallType.Open && visited[x - 1, y] == false)
                    visited[x - 1, y] = true;
                if (room.North == WallType.Open && visited[x, y - 1] == false)
                    visited[x, y - 1] = true;
                if (room.South == WallType.Open && visited[x, y + 1] == false)
                    visited[x, y + 1] = true;
            }
            return wall;
        }

        private void DoAttack(PlayerIndex i, Character opponent)
        {
            var player = playerMap[i];
            var playerRoll = rnd.Next(21);
            var creatureRoll = rnd.Next(21);
            if (playerRoll + player.ToHit - opponent.Avoidance > 0)
            {
                //hit
                opponent.Health -= player.RollDamage();

            }
            if (opponent.Health > 0 && creatureRoll + opponent.ToHit - player.Avoidance > 0)
            {
                //hit player
                player.Health -= opponent.RollDamage();
            }
            if (opponent.Health < 1)
            {
                objectLocationMap.Remove(opponent);
                player.Score += opponent.MaxHealth;

            }

        }
    }
}
